/* Copyright 2009
 *
 * This program and the accompanying materials
 * are made available under the terms of the
 * Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at
 *
 * 		http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * Contributors:
 * 	   IBM Corporation - initial API and implementation for JDT/DLTK
 *     Sean W. Quinn - initial adoption for use with PHP from various sources.
 */
package org.eclipse.php.internal.ui.preferences.formatter;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.dltk.ui.formatter.AlreadyExistsDialog;
import org.eclipse.dltk.ui.formatter.CreateProfileDialog;
import org.eclipse.dltk.ui.formatter.FormatterModifyDialog;
import org.eclipse.dltk.ui.formatter.IProfile;
import org.eclipse.dltk.ui.formatter.IProfileManager;
import org.eclipse.dltk.ui.formatter.IProfileStore;
import org.eclipse.dltk.ui.util.ExceptionHandler;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.StatusDialog;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.window.Window;
import org.eclipse.php.internal.ui.PHPUiPluginExt;
import org.eclipse.php.internal.ui.preferences.PreferencesAccess;
import org.eclipse.php.internal.ui.preferences.PreferencesMessages;
import org.eclipse.php.internal.ui.preferences.formatter.profile.CustomProfile;
import org.eclipse.php.internal.ui.preferences.formatter.profile.PHPProfile;
import org.eclipse.php.internal.ui.preferences.formatter.profile.PHPProfileManager;
import org.eclipse.php.internal.ui.preferences.formatter.profile.PHPProfileStore;
import org.eclipse.php.internal.ui.preferences.formatter.profile.PHPProfileVersioner;
import org.eclipse.php.internal.ui.util.ControlUtils;
import org.eclipse.php.internal.ui.util.Messages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

/**
 * @author Sean W. Quinn (swquinn@gmail.com)
 */
@SuppressWarnings("restriction")
public abstract class ProfileConfigurationBlock {

  /**
   * @author Sean W. Quinn (swquinn@gmail.com)
   */
  class ButtonController implements Observer, SelectionListener {

    /**
		 *
		 */
    public ButtonController() {
      profileManager.addObserver(this);
      newButton.addSelectionListener(this);
      editButton.addSelectionListener(this);
      removeButton.addSelectionListener(this);
      importButton.addSelectionListener(this);
      update(profileManager, null);
    }

    /**
     * {@inheritDoc}
     *
     * <p>
     * If the selected profile from the {@link PHPProfileManager} is not a built
     * in profile, we want to make sure that the delete button is enabled, other
     * wise it should be disabled.
     * </p>
     */
    public void update(final Observable observable, final Object arg) {
      final IProfile selected = ((PHPProfileManager) observable).getSelected();
      final boolean notBuiltIn = !selected.isBuiltInProfile();
      removeButton.setEnabled(notBuiltIn);
    }

    /**
     * <p>
     * Processes the selection event of a particular widget. In this case, it is
     * a <tt>button</tt>.
     * </p>
     *
     * @param event the {@link SelectionEvent}
     */
    public void widgetSelected(final SelectionEvent event) {
      final Button button = (Button) event.widget;
      if (button == editButton) {
        modifyButtonPressed();
      }
      else if (button == removeButton) {
        deleteButtonPressed();
      }
      else if (button == newButton) {
        newButtonPressed();
      }
      else if (button == importButton) {
        loadButtonPressed();
      }
    }

    /**
     * <p>
     * Sets the widget that is by default selected before a user does anything.
     * </p>
     *
     * @param event the {@link SelectionEvent}
     */
    public void widgetDefaultSelected(final SelectionEvent event) {
      // Empty, no widget (button) by default it selected.
    }

    /**
     *
     */
    private void modifyButtonPressed() {
      final StatusDialog modifyDialog = createModifyDialog(composite.getShell(), profileManager.getSelected(),
          profileManager, profileStore, false);
      modifyDialog.open();
    }

    /**
     * <p>
     * Method that is run when the user clicks on the button to delete a
     * profile, this will prompt the user to confirm whether or not he really
     * wants to proceed with this action.
     * </p>
     */
    private void deleteButtonPressed() {
      final String title = PHPFormatterMessages.CodingStyleConfigurationBlock_delete_confirmation_title;
      final String msg = Messages.format(
          PHPFormatterMessages.CodingStyleConfigurationBlock_delete_confirmation_question, profileManager.getSelected()
              .getName());
      if (MessageDialog.openQuestion(composite.getShell(), title, msg)) {
        profileManager.deleteSelected();
      }
    }

    /**
     *
     */
    private void newButtonPressed() {
      final CreateProfileDialog profileDialog = new CreateProfileDialog(composite.getShell(), profileManager,
          profileVersioner);

      if (profileDialog.open() != Window.OK) {
        return;
      }

      if (!profileDialog.openEditDialog()) {
        return;
      }

      final StatusDialog modifyDialog = createModifyDialog(composite.getShell(), profileDialog.getCreatedProfile(),
          profileManager, profileStore, true);
      modifyDialog.open();
    }

    /**
     *
     */
    private void loadButtonPressed() {
      final FileDialog fileDialog = new FileDialog(composite.getShell(), SWT.OPEN);
      final String key = lastSaveLoadPathKey + ".loadpath";
      fileDialog.setText(PHPFormatterMessages.CodingStyleConfigurationBlock_load_profile_dialog_title);
      fileDialog.setFilterExtensions(new String[] { "*.xml" });

      final String lastPath = PHPUiPluginExt.getDefault().getDialogSettings().get(key);

      if (lastPath != null) {
        fileDialog.setFilterPath(lastPath);
      }

      final String path = fileDialog.open();
      if (path == null) {
        return;
      }

      PHPUiPluginExt.getDefault().getDialogSettings().put(key, fileDialog.getFilterPath());
      final File file = new File(path);
      Collection<IProfile> profiles = null;
      try {
        profiles = profileStore.readProfilesFromFile(file);
      }
      catch (CoreException ex) {
        final String title = PHPFormatterMessages.CodingStyleConfigurationBlock_load_profile_error_title;
        final String message = PHPFormatterMessages.CodingStyleConfigurationBlock_load_profile_error_message;
        ExceptionHandler.handle(ex, composite.getShell(), title, message);
      }

      if (profiles == null || profiles.isEmpty()) {
        return;
      }

      final CustomProfile profile = (CustomProfile) profiles.iterator().next();
      if (!profileVersioner.getProfileKind().equals(profile.getKind())) {
        final String title = PHPFormatterMessages.CodingStyleConfigurationBlock_load_profile_error_title;
        final String message = Messages.format(
            PHPFormatterMessages.ProfileConfigurationBlock_load_profile_wrong_profile_message, new String[] {
                profileVersioner.getProfileKind(), profile.getKind() });
        MessageDialog.openError(composite.getShell(), title, message);
        return;
      }

      if (profile.getVersion() > profileVersioner.getCurrentVersion()) {
        final String title = PHPFormatterMessages.CodingStyleConfigurationBlock_load_profile_error_too_new_title;
        final String message = PHPFormatterMessages.CodingStyleConfigurationBlock_load_profile_error_too_new_message;
        MessageDialog.openWarning(composite.getShell(), title, message);
      }

      if (profileManager.containsName(profile.getName())) {
        final AlreadyExistsDialog alreadyExistsDialog = new AlreadyExistsDialog(composite.getShell(), profile,
            profileManager);
        if (alreadyExistsDialog.open() != Window.OK) {
          return;
        }
        profileVersioner.update(profile);
        profileManager.addProfile(profile);
      }
    }
  }

  /**
   * The GUI controls
   */
  private Composite composite;

  /**
   * The active profile combo box. This is the control that stores a list of
   * selectable profiles to select, edit, and remove.
   */
  private Combo activeProfileCombo;

  /**
   * The "<tt>Edit...</tt>" button--allowing a user to make changes to an
   * existing profile.
   */
  private Button editButton;

  /** The "<tt>Remove</tt>" button--allows a user to remove a profile. */
  private Button removeButton;

  /** The "<tt>New...</tt>" button, creates a new profile. */
  private Button newButton;

  /** The "<tt>Import...</tt>" button. */
  private Button importButton;

  private PixelConverter fPixConv;

  /**
   * The ProfileManager, the model of this page.
   */
  private final PHPProfileManager profileManager;
  private final PHPProfileVersioner profileVersioner;
  private final PreferencesAccess preferenceAccess;
  private final IScopeContext currentContext;
  private final IScopeContext instanceScope;
  private final PHPProfileStore profileStore;
  private final String lastSaveLoadPathKey;

  /**
   * @param project
   * @param access
   * @param lastSaveLoadPathKey
   */
  public ProfileConfigurationBlock(final IProject project, final PreferencesAccess preferenceAccess,
      final String lastSaveLoadPathKey) {
    this.preferenceAccess = preferenceAccess;
    this.lastSaveLoadPathKey = lastSaveLoadPathKey;
    this.profileVersioner = createProfileVersioner();
    this.profileStore = createProfileStore(profileVersioner);

    this.instanceScope = preferenceAccess.getInstanceScope();
    this.currentContext = (project != null) ? preferenceAccess.getProjectScope(project) : this.instanceScope;

    // **
    // Instantiate the profiles that will be added to the ProfileManager.
    List<PHPProfile> profiles = null;
    if (profiles == null) {
      profiles = new ArrayList<PHPProfile>();
    }

    this.profileManager = createProfileManager(profiles, this.currentContext, preferenceAccess, this.profileVersioner);

    // TODO: Implement preference listener.
  }

  protected void preferenceChanged(PreferenceChangeEvent event) {

  }

  /**
   *
   * @param shell
   * @param profile
   * @param profileManager
   * @param profileStore
   * @param newProfile
   * @return
   */
  protected abstract FormatterModifyDialog createModifyDialog(final Shell shell, final IProfile profile,
      final IProfileManager profileManager, final IProfileStore profileStore, final boolean newProfile);

  /**
   * <p>
   * Creates a {@link PHPProfileVersioner} specific to the implementation of
   * this <code>ProfileConfigurationBlock</code>.
   * </p>
   *
   * @return the {@link PHPProfileVersioner} created for this block.
   */
  protected abstract PHPProfileVersioner createProfileVersioner();

  /**
   * <p>
   * Creates a {@link PHPProfileStore} given a {@link PHPProfileVersioner}.
   * </p>
   *
   * @param versioner the profile versioner to use when creating the store.
   * @return the created <code>ProfileStore</code>.
   */
  protected abstract PHPProfileStore createProfileStore(PHPProfileVersioner versioner);

  /**
   * @param profiles
   * @param context
   * @param preferenceAccess
   * @param profileVersioner
   * @return
   */
  protected abstract PHPProfileManager createProfileManager(final List<PHPProfile> profiles,
      final IScopeContext context, final PreferencesAccess preferenceAccess, final PHPProfileVersioner profileVersioner);

  //  protected abstract ModifyDialog createModifyDialog(final Shell shell, final Profile profile, final ProfileManager profileManager,
  //      final ProfileStore profileStore, final boolean newProfile);

  protected abstract void configurePreview(Composite composite, int numColumns, PHPProfileManager profileManager);

  /**
   * Create the contents
   *
   * @param parent Parent composite
   * @return Created control
   */
  public Composite createContents(Composite parent) {
    final int numColumns = 5;

    fPixConv = new PixelConverter(parent);

    composite = ControlUtils.createComposite(parent, numColumns);

    Label profileLabel = new Label(composite, SWT.NONE);
    profileLabel.setText(PreferencesMessages.CleanUpPreferencePage_Description);
    GridData data = new GridData(SWT.FILL, SWT.FILL, true, false);
    data.horizontalSpan = numColumns;
    profileLabel.setLayoutData(data);

    // ** Create the active profile combobox/dropdown.
    activeProfileCombo = createProfileCombo(composite, 3, fPixConv.convertWidthInCharsToPixels(20));

    // ** Create the Edit button.
    editButton = ControlUtils.createButton(composite,
        PHPFormatterMessages.CodingStyleConfigurationBlock_edit_button_desc, GridData.HORIZONTAL_ALIGN_BEGINNING);

    // ** Create the remove button.
    removeButton = ControlUtils.createButton(composite,
        PHPFormatterMessages.CodingStyleConfigurationBlock_remove_button_desc, GridData.HORIZONTAL_ALIGN_BEGINNING);

    // ** Create the new button.
    newButton = ControlUtils.createButton(composite,
        PHPFormatterMessages.CodingStyleConfigurationBlock_new_button_desc, GridData.HORIZONTAL_ALIGN_BEGINNING);

    // ** Create the import button.
    importButton = ControlUtils.createButton(composite,
        PHPFormatterMessages.CodingStyleConfigurationBlock_load_button_desc, GridData.HORIZONTAL_ALIGN_END);

    ControlUtils.createLabel(composite, "", 3); //$NON-NLS-1$
    configurePreview(composite, numColumns, profileManager);

    new ButtonController();
    //new ProfileComboController();
    return composite;
  }

  /**
   * <p>
   * The method that is executed when the user clicks on the <tt>OK</tt> button.
   * </p>
   *
   * @return <tt>true</tt> when the user clicks <tt>OK</tt>.
   */
  public boolean performOk() {
    return true;
  }

  /**
   * <p>
   * </p>
   */
  public void performApply() {
    //    try {
    //      fCurrContext.getNode(PHPUiHelper.UI_PLUGIN_ID).flush();
    //      fCurrContext.getNode(PHPCoreConstantsExt.PLUGIN_ID).flush();
    //      if (fCurrContext != fInstanceScope) {
    //        fInstanceScope.getNode(PHPUiHelper.UI_PLUGIN_ID).flush();
    //        fInstanceScope.getNode(PHPCoreConstantsExt.PLUGIN_ID).flush();
    //      }
    //    }
    //    catch (BackingStoreException e) {
    //      JavaPlugin.log(e);
    //    }
  }

  /**
   *
   */
  public void performDefaults() {
    //    Profile profile = fProfileManager.getDefaultProfile();
    //    if (profile != null) {
    //      int defaultIndex = fProfileManager.getSortedProfiles().indexOf(profile);
    //      if (defaultIndex != -1) {
    //        fProfileManager.setSelected(profile);
    //      }
    //    }
  }

  /**
   * <p>
   * </p>
   *
   * @param composite
   * @param span
   * @param widthHint
   * @return
   */
  public static Combo createProfileCombo(Composite composite, int span, int widthHint) {
    final GridData gd = new GridData(GridData.FILL_HORIZONTAL);
    gd.horizontalSpan = span;
    gd.widthHint = widthHint;

    final Combo combo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
    combo.setFont(composite.getFont());
    ControlUtils.setDefaultVisibleItemCount(combo);
    combo.setLayoutData(gd);
    return combo;
  }
}
